home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
edit-text.lisp
< prev
next >
Wrap
Text File
|
1991-07-15
|
37KB
|
1,002 lines
;;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714-9149 |
;;; |
;;; Copyright (C) 1990, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(export '(
edit-text
edit-text-clear
edit-text-cut
edit-text-field
edit-text-grow
edit-text-field-length
edit-text-paste
make-edit-text
make-edit-text-field
)
'clio-open)
(defmacro char-or-keysym (keysym)
;; Expands to the character corresponding to the KEYSYM in the
;; default global (display-independent) keysym mapping, if any.
;; Otherwise, expands to the KEYSYM.
(let ((mapping (find-if #'(lambda (mapping)
;; Better to use keysym-mapping accessors directly, but in R3 CLX these
;; macros are defined only at compile time.
(and (characterp (first mapping)) ; xlib::keysym-mapping-object
(not (second mapping)) ; xlib::keysym-mapping-mask
(not (third mapping)) ; xlib::keysym-mapping-modifiers
(not (fourth mapping)) ; xlib::keysym-mapping-lowercase
(not (fifth mapping)) ; xlib::keysym-mapping-translate
))
(gethash keysym xlib::*keysym->character-map*))))
`,(if mapping
(first mapping) ; xlib::keysym-mapping-object
keysym)))
(defconstant
*default-edit-text-field-command-table*
(make-text-command-table
:default 'text-insert
#\rubout 'text-rubout
#\newline 'text-complete
#\linefeed 'text-complete
(char-or-keysym #.(xlib::keysym 255 83)) '(text-move-point :chars 1) ; Right Arrow
(char-or-keysym #.(xlib::keysym 255 81)) '(text-move-point :chars -1) ; Left Arrow
(char-or-keysym #.(xlib::keysym 255 82)) 'ignore ; Up Arrow
(char-or-keysym #.(xlib::keysym 255 84)) 'ignore ; Down Arrow
;; KCL doesn't support char-bits!
#-kcl #\Control-\y #-kcl 'edit-text-paste
#-kcl #\Control-\w #-kcl 'edit-text-cut
#-kcl #\Meta-\w #-kcl 'display-text-copy
#-kcl #\Control-\a #-kcl '(text-move-sol)
#-kcl #\Control-\e #-kcl '(text-move-eol)
#-kcl #\Control-\k #-kcl '(text-delete-eol)
))
;;;----------------------------------------------------------------------------+
;;; |
;;; text-editor |
;;; |
;;;----------------------------------------------------------------------------+
(defconstant *i-bar-cursor-index* 152)
(defcontact text-editor ()
((commands :type list
:initform (list *default-edit-text-field-command-table*)
:initarg :commands
:accessor edit-text-commands)
(focus-p :type boolean
:initform nil
:accessor edit-text-focus-p))
(:resources
(cursor :initform *i-bar-cursor-index* :type cursor))
(:documentation "Basic behaviors for editing text."))
;;;----------------------------------------------------------------------------+
;;; |
;;; Event Handling |
;;; |
;;;----------------------------------------------------------------------------+
(defevent text-editor (:button-press :button-1 :control) edit-text-cut)
(defevent text-editor :enter-notify (change-focus t))
(defevent text-editor :leave-notify (change-focus nil))
(defevent text-editor :focus-out (change-focus nil t))
(defevent text-editor :focus-in (change-focus t t))
(defevent text-editor :key-press perform-command)
(defun change-focus (text new-value &optional explicit-p)
(with-event (focus-p kind)
(when
(and
;; Text window actually the one gaining/losing focus?
(if explicit-p
(member kind '(:ancestor :inferior :nonlinear))
focus-p)
;; Actually losing when leaving?
(or new-value explicit-p (not (eq (input-focus (contact-display text)) text))))
(setf (edit-text-focus-p text) new-value))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Display |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod (setf text-caret-displayed-p) (boolean (text text-editor)
&optional exposed-x exposed-y exposed-width exposed-height)
(unless (or (not (realized-p text)) (text-selection-range text))
(with-slots (focus-p point foreground) text
(let*
((scale (contact-scale text))
(caret (getf *text-caret-dimensions* scale))
(offset (text-caret-baseline-offset caret)))
;; Get image and dimensions for active/inactive caret.
(multiple-value-bind (width height image)
(if focus-p
(values
(text-caret-width caret)
(text-caret-height caret)
(getf (getf *text-caret-images* :active) scale))
(values
nil
(or (text-caret-inactive-height caret) (text-caret-height caret))
(getf (getf *text-caret-images* :inactive) scale)))
;; Adjust amount of image to copy.
(setf width (or width height)
height (min height (+ (text-caret-descent text scale) offset)))
;; Copy image pixmap.
(multiple-value-bind (x y) (text-base-position text point)
(using-gcontext (gc :drawable text :function boole-xor :exposures :off)
(with-gcontext (gc :clip-mask (when exposed-x (list exposed-x exposed-y exposed-width exposed-height)))
(copy-area
(contact-image-mask
text image
:foreground (logxor foreground (contact-current-background-pixel text)))
gc
0 0 width height
text
(1+ (- x (pixel-round width 2))) (- y offset)))))))))
boolean)
(defgeneric text-caret-descent (text scale)
(:documentation "Return the descent of the displayed caret for TEXT."))
(defmethod text-caret-descent ((text text-editor) scale)
(let ((dimensions (getf *text-caret-dimensions* scale)))
(- (or (text-caret-inactive-height dimensions)
(text-caret-height dimensions))
(text-caret-baseline-offset dimensions))))
(defmethod compute-text-geometry :around ((text text-editor))
(with-slots (gravity) text
(multiple-value-bind (left top width height ascent descent)
(call-next-method)
(values
;; Leave room for caret at end.
(case gravity
((:north-west :west :south-west)
(+ left (pixel-round (text-caret-width (getf *text-caret-dimensions* (contact-scale text))) 2)))
((:north-east :east :south-east)
(- left (pixel-round (text-caret-width (getf *text-caret-dimensions* (contact-scale text))) 2)))
(otherwise
left))
top width height ascent descent))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Selection |
;;; |
;;;----------------------------------------------------------------------------+
(defgeneric edit-text-clear (text)
(:documentation "Sets the source of the TEXT to the empty string."))
(defmethod edit-text-clear ((text text-editor))
(setf (display-text-source text) ""))
(defgeneric edit-text-cut (text)
(:documentation "Causes the TEXT selection to be deleted into the :CLIPBOARD.
Returns the deleted text."))
(defmethod edit-text-cut ((text text-editor))
(let ((clip (clipboard-copy text)))
(when clip (text-rubout text))
clip))
(defgeneric edit-text-paste (text)
(:documentation "Inserts the :CLIPBOARD into the TEXT and returns the inserted string."))
(defmethod edit-text-paste ((text text-editor))
(let*
((display (contact-display text))
(client-clip (display-clipboard-text display))
(paste
;; Does this client own the :CLIPBOARD selection?
(if (plusp (length client-clip))
;; Yes, get it the easy way.
client-clip
;; No, use interclient communication.
(flet
((throw-convert (text)
(declare (ignore text))
(with-event (property) (throw :convert property))))
(let ((time (when (processing-event-p) (with-event (time) time))))
(with-event-mode (text `(:selection-notify ,#'throw-convert))
(convert-selection :clipboard :string text :paste time)
;; Wait for :selection-notify to report result of conversion.
(when (catch :convert (loop (process-next-event display)))
;; Conversion successful --- get stored value.
(get-property
text :paste :result-type 'string
;; The :string target specifies Latin-1 encoding. This happens to correspond
;; to the keysym encoding, hence the following transform function.
;; Note that #'code-char might work on many systems, but this is not guaranteed
;; since Common Lisp does not specify a standard character encoding.
:transform #'(lambda (code) (keysym->character display code))))))))))
(if paste
(text-insert text paste)
(bell display))
paste))
;;;----------------------------------------------------------------------------+
;;; |
;;; Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod (setf edit-text-focus-p) :around (new-value (text text-editor))
(with-slots (focus-p) text
(let* ((changed-p (if new-value (not focus-p) focus-p))
(caret-p (and changed-p (not (text-selection-range text)))))
(when caret-p
(setf (text-caret-displayed-p text) nil))
(call-next-method)
(when changed-p
(when caret-p
(setf (text-caret-displayed-p text) t))
(apply-callback text (if new-value :resume :suspend)))))
new-value)
;;;----------------------------------------------------------------------------+
;;; |
;;; edit-text-field |
;;; |
;;;----------------------------------------------------------------------------+
(defcontact edit-text-field (text-editor select-text display-text-field)
((length :type (or null (integer 0 *))
:initform nil
:initarg :length
:accessor edit-text-field-length))
(:resources
(font :initform *default-display-text-font*)
(display-gravity :initform :west)
length)
(:documentation "A single line of editable text."))
(defun make-edit-text-field (&rest initargs)
(apply #'make-contact 'edit-text-field initargs))
;;;----------------------------------------------------------------------------+
;;; |
;;; Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod (setf edit-text-point) :before (new-point (text edit-text-field) &key clear-p)
(declare (ignore clear-p))
(check-type new-point (or null (integer 0 *))))
(defmethod (setf edit-text-mark) :before (new-mark (text edit-text-field))
(check-type new-mark (or null (integer 0 *))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Display |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod display :around ((text edit-text-field) &optional x y width height &key)
;; Display underline
(multiple-value-bind (base-x base-y) (call-next-method)
(let ((scale (contact-scale text)))
(with-slots (foreground font length width clip-rectangle) text
(let*
((underline-y (+ base-y (text-caret-descent text scale)))
;; Length of line reflects max string length
(start-x (if length
base-x
0))
(end-x (if length
(+ base-x
(* length
;; Use average char width and hope for the best.
(pixel-round (+ (min-char-width font) (max-char-width font)) 2)))
width)))
(using-gcontext (gc :drawable text
:foreground foreground
:clip-mask clip-rectangle)
(draw-line text gc start-x underline-y end-x underline-y))))))
;; Display caret, current selection
(setf (text-selection-displayed-p text x y width height) t)
(setf (text-caret-displayed-p text) t))
(defmethod text-clear-line ((text edit-text-field) base-x base-y)
(with-slots (font) text
(clear-area
text
:x base-x
:y (- base-y (font-ascent font))
:height (+ (font-ascent font) (text-caret-descent text (contact-scale text))))))
(defmethod text-change-highlight ((text edit-text-field) from to
&optional exposed-x exposed-y exposed-width exposed-height)
(when (realized-p text)
(with-slots (font foreground clip-rectangle) text
(let ((ascent (font-ascent font))
(descent (font-descent font)))
(multiple-value-bind (from-x from-y)
(text-mark-point text from)
(let ((to-x (text-mark-point text to)))
(using-gcontext
(gc :drawable text
:function boole-xor
:clip-mask clip-rectangle
:foreground (logxor
foreground
(contact-current-background-pixel text)))
(if exposed-x
;; Clip highlight to intersection of clip rectangle and exposed region.
(let
((old-clip-x (display-clip-x text))
(old-clip-y (display-clip-y text))
(old-clip-width (display-clip-width text))
(old-clip-height (display-clip-height text)))
(setf
(display-clip-x text) (max old-clip-x exposed-x)
(display-clip-y text) (max old-clip-y exposed-y)
(display-clip-width text) (- (min (+ exposed-x exposed-width)
(+ old-clip-x old-clip-width))
(display-clip-x text))
(display-clip-height text) (- (min (+ exposed-y exposed-height)
(+ old-clip-y old-clip-height))
(display-clip-y text)))
;; Does intersection exist?
(when (and (plusp (display-clip-width text)) (plusp (display-clip-height text)))
(with-gcontext (gc :clip-mask clip-rectangle)
(draw-rectangle
text gc
(min from-x to-x) (- from-y ascent)
(abs (- from-x to-x))
(+ ascent descent)
t)))
;; Restore clip rectangle
(setf (display-clip-x text) old-clip-x
(display-clip-y text) old-clip-y
(display-clip-width text) old-clip-width
(display-clip-height text) old-clip-height))
;; Else draw highlight without additional clipping
(draw-rectangle
text gc
(min from-x to-x) (- from-y ascent)
(abs (- from-x to-x))
(+ ascent descent)
t)))))))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Geometry |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod preferred-size ((text edit-text-field) &key width height border-width)
(with-slots
(length font (contact-width width) (contact-height height) (contact-border-width border-width))
text
(multiple-value-bind (text-width text-height)
(if length
;; Prefer to be big enough for length chars (use average char width and hope for the best).
(values (* length (pixel-round (+ (min-char-width font) (max-char-width font)) 2))
(+ (font-ascent font) (font-descent font)))
;; Else use current source extent.
(display-text-extent text))
(let ((scale (contact-scale text)))
(values
;; Ensure wide enough to display caret at end.
(max (+ text-width (text-caret-width (getf *text-caret-dimensions* scale))) (or width contact-width))
;; Ensure tall enough to display caret and underline.
(max (+ text-height (text-caret-descent text scale) 1) (or height contact-height))
(or border-width contact-border-width))))))
(defmethod text-caret-descent :around ((text edit-text-field) scale)
;; Decrement normal caret height to avoid underline.
(1- (call-next-method)))
(defmethod display-text-extent :around ((text edit-text-field))
(multiple-value-bind (width height ascent) (call-next-method)
(declare (ignore height))
(let ((descent (1+ (text-caret-descent text (contact-scale text)))))
(values width (+ ascent descent) ascent descent))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Command Functions |
;;; |
;;;----------------------------------------------------------------------------+
(defun perform-command (edit-text)
(with-slots (commands) edit-text
(with-event (character keysym)
(let ((input (or character keysym)))
;; Look up command in command table list.
(multiple-value-bind (command default)
(dolist (table commands)
(let* ((command (text-command table input))
(default (unless command (text-command table :default))))
(when (or command default)
(return (values command default)))))
(cond
;; Command found --- call with edit-text and other args.
(command
(if (listp command)
(apply (first command) edit-text (rest command))
(funcall command edit-text)))
;; Default command found --- call with edit-text, input, and other args.
(default
(if (listp default)
(apply (first default) edit-text input (rest default))
(funcall default edit-text input)))))))))
(defgeneric text-insert (edit-text chars)
(:documentation "Insert the CHARS into the EDIT-TEXT at the current point
and increment the point."))
(defmethod text-insert ((text text-editor) input)
;; If input not character or string, then ignore.
;; This case may occur for non-character keysyms like arrow keys.
(declare (ignore input)))
(defmethod text-insert :around ((text text-editor) (input character))
;; Ignore non-graphic characters (e.g. #\Hyper-Q).
(if (graphic-char-p input)
(call-next-method)
(text-insert-nongraphic text input)))
(defgeneric text-insert-nongraphic (text input)
(:documentation "Insert non-graphic INPUT into the EDIT-TEXT at the current point."))
(defmethod text-insert-nongraphic ((text text-editor) input)
(declare (ignore input))
(bell (contact-display text)))
(defmethod edit-text-field-insert ((text edit-text-field) char)
(declare (type edit-text-field text)
(type (or character string) char))
(with-slots (buffer mark point gravity length)
text
(multiple-value-bind (select-start select-end) (text-selection-range text)
;; Invoke :insert callback
(let ((initial-insert-point (or select-start point)))
(multiple-value-bind (insert-point char)
(apply-callback-else (text :insert text initial-insert-point char)
(values initial-insert-point char))
(when
(or
;; Insertion refused?
(not insert-point)
;; Too many chars?
(and length (>= (buffer-length buffer) length)
(bell (contact-display text)) t))
;; Insertion not allowed.
(return-from edit-text-field-insert))
;; If insert point altered, then clear selection and do not delete it.
(unless (or (not select-start) (= insert-point initial-insert-point))
(setf (edit-text-mark text) point
select-start nil))
(while-changing-marks (text)
(let* ((clear-all-p
(case gravity
((:north-west :west :south-west) select-start)
(otherwise t)))
(clear-position
(if clear-all-p 0 insert-point)))
;; Clear before changing source.
(multiple-value-bind (base-x base-y)
(text-base-position text clear-position)
(text-clear-line text base-x base-y)
;; Delete current selection.
(when select-start
(buffer-delete buffer select-start select-end))
;; Insert new character and move point
(let ((new-point (buffer-line-insert buffer char insert-point)))
;; Refresh new line
(text-refresh-line
text clear-position
:clear-p nil
:base-x (unless clear-all-p base-x)
:base-y base-y)
;; Update point, mark.
(setf mark (setf point new-point)))))))))))
(defmethod text-insert ((text edit-text-field) (char character))
(edit-text-field-insert text char))
(defmethod text-insert ((text edit-text-field) (char string))
(edit-text-field-insert text char))
(defgeneric text-move-point (edit-text &key lines chars)
(:documentation "Increment the point of the EDIT-TEXT by the
given number of LINES and CHARS."))
(defmethod text-move-point ((text text-editor) &key (lines 0) (chars 0))
(with-slots (point mark buffer) text
(while-changing-marks (text)
(let ((new-point (buffer-move-mark buffer point :chars chars :lines lines)))
(if (text-selection-range text)
(text-change-highlight text point new-point)
(setf mark (move-mark mark new-point)))
(setf point (move-mark point new-point))))
(apply-callback text :point text (buffer-mark-position buffer point))))
(defgeneric text-move-sol (edit-text)
(:documentation "Move to the start of the current line of EDIT-TEXT."))
(defmethod text-move-sol ((text text-editor))
(with-slots (point buffer) text
(setf (edit-text-point text :clear-p (not (text-selection-range text)))
(buffer-sol buffer point))))
(defgeneric text-move-eol (edit-text)
(:documentation "Move to the end of the current line of EDIT-TEXT."))
(defmethod text-move-eol ((text text-editor))
(with-slots (point buffer) text
(setf (edit-text-point text :clear-p (not (text-selection-range text)))
(buffer-eol buffer point))))
(defgeneric text-delete-eol (edit-text)
(:documentation "Delete to the end of the current line of EDIT-TEXT."))
(defmethod text-delete-eol ((text text-editor))
(with-slots (point buffer) text
;; Select to end of line...
(setf (edit-text-mark text) (buffer-eol buffer point))
;; ...and delete it.
(text-rubout text)))
(defgeneric text-rubout (edit-text)
(:documentation "Decrement the current point and delete the character in the EDIT-TEXT
at the new point."))
(defmethod text-rubout ((text edit-text-field))
(with-slots (point mark gravity buffer) text
(multiple-value-bind (select-start select-end) (text-selection-range text)
;; Attempt to delete non-existent character?
(if (and (not select-start) point (zerop point))
;; Yes, beep a warning.
(bell (contact-display text))
;; No, perform delete.
(let ((initial-start (or select-start (buffer-move-mark buffer point :chars -1)))
(initial-end (or select-end point)))
;; Invoke :delete callback.
(multiple-value-bind (start end)
(apply-callback-else (text :delete text initial-start initial-end)
(values initial-start initial-end))
;; Deletion allowed?
(unless start (return-from text-rubout))
;; If delete range altered, then clear selection and do not delete it.
(unless (and (= start initial-start) (= end initial-end))
(setf (edit-text-mark text) point
select-start nil))
(let*
((clear-all-p
(case gravity
((:north-west :west :south-west) select-start)
(otherwise t)))
(clear-position
(if clear-all-p 0 start)))
(while-changing-marks (text)
;; Clear before changing source.
(multiple-value-bind (base-x base-y) (text-base-position text clear-position)
(text-clear-line text base-x base-y)
;; Delete chars and reset point, mark.
(buffer-line-delete buffer (setf point (setf mark start)) end)
;; Redisplay chars
(text-refresh-line
text clear-position
:clear-p nil
:base-x (unless clear-all-p base-x)
:base-y base-y))))))))))
(defgeneric text-complete (edit-text)
(:documentation "Invoke the :complete callback."))
(defmethod text-complete ((text text-editor))
(multiple-value-bind (verified-p message)
(apply-callback-else (text :verify text)
t)
(if verified-p
(apply-callback text :complete)
(confirm-p
:near text
:message (or message "Text changes not accepted.")
:accept-only :on))))
;;;----------------------------------------------------------------------------+
;;; |
;;; edit-text |
;;; |
;;;----------------------------------------------------------------------------+
(defconstant
*default-edit-text-command-table*
(make-text-command-table
:default 'text-insert
#\rubout 'text-rubout
(char-or-keysym #.(xlib::keysym 255 83)) '(text-move-point :chars 1) ; Right Arrow
(char-or-keysym #.(xlib::keysym 255 81)) '(text-move-point :chars -1) ; Left Arrow
(char-or-keysym #.(xlib::keysym 255 82)) '(text-move-point :lines -1) ; Up Arrow
(char-or-keysym #.(xlib::keysym 255 84)) '(text-move-point :lines 1) ; Down Arrow
;; KCL doesn't support char-bits!
#-kcl #\Control-\y #-kcl 'edit-text-paste
#-kcl #\Control-\w #-kcl 'edit-text-cut
#-kcl #\Meta-\w #-kcl 'display-text-copy
#-kcl #\Control-\a #-kcl '(text-move-sol)
#-kcl #\Control-\e #-kcl '(text-move-eol)
#-kcl #\Control-\k #-kcl '(text-delete-eol)
))
(defcontact edit-text (text-editor display-text)
((commands :initform (list *default-edit-text-command-table*)))
(:resources
(display-gravity :initform :north-west))
(:documentation "Multiple lines of editable text."))
(defun make-edit-text (&rest initargs)
(apply #'make-contact 'edit-text initargs))
;;;----------------------------------------------------------------------------+
;;; |
;;; Command Functions |
;;; |
;;;----------------------------------------------------------------------------+
(let ((insert-start (make-mark))
(insert-mark (make-mark)))
(flet
((edit-text-insert (text string)
(declare (type edit-text text)
(type (or character string) string))
(with-slots (buffer mark point font gravity alignment extent-left extent-width) text
(multiple-value-bind (select-start select-end) (text-selection-range text)
;; Initialize insert mark.
(move-mark insert-start (or select-start point))
;; Invoke :insert callback, if necessary
(multiple-value-bind (insert-pos string)
(apply-callback-else
(text :insert text (buffer-mark-position buffer insert-start) string)
(values t string))
;; Insert allowed?
(unless insert-pos (return-from edit-text-insert))
;; New insert position returned?
(unless (eq insert-pos t)
;; Yes, convert to insert mark.
(buffer-position-mark buffer insert-pos insert-start))
;; If insert point altered, then clear selection and do not delete it.
(unless (or (not select-start) (mark-equal insert-start select-start))
(setf (edit-text-mark text) point
select-start nil))
(while-changing-marks (text)
(let ((small-delete-p
(cond
(select-start
;; Delete current selection, if any.
(buffer-delete buffer select-start select-end)
;; Return true if delete limited to one line.
(= (mark-line-index select-end) (mark-line-index select-start)))
(:else
t))))
;; Insert new string and move insert mark
(move-mark insert-mark insert-start)
(buffer-insert buffer string insert-mark)
(move-mark mark (move-mark point insert-mark))
;; Redisplay is simple and efficient for most common case ---
;; :north-west gravity, :left alignment, and insert/delete affecting only one line.
;; Otherwise, redisplay is simple and inefficient! Replace with more
;; sophisticated algorithm when possible.
(multiple-value-bind (refresh-start refresh-end clear-p)
(if
(or (and (eq gravity :north-west) (eq alignment :left))
(and (eq gravity :north-east) (eq alignment :right)))
;; Optimize this case...
(let*
((one-line-p (and small-delete-p
(= (mark-line-index insert-mark)
(mark-line-index insert-start))))
(ascent (font-ascent font))
(descent (font-descent font))
(clear-start (mark-line-index insert-start))
(line-height (+ ascent descent)))
;; Clear damaged areas. If multiple lines damaged, just clear to bottom of window.
(when (eq alignment :left)
;; This case can be optimized: clear first line only from insert point.
(text-clear-line
text
(text-base-x text clear-start (mark-index insert-start))
(text-base-y text clear-start)))
(unless (and (eq alignment :left) one-line-p)
(when (eq alignment :left)
;; First line already cleared above.
(incf clear-start))
;; Clear one or more lines.
(clear-area
text
:x extent-left
:y (- (text-base-y text clear-start) ascent)
:width extent-width
:height (when one-line-p line-height)))
;; If multiple lines damaged, just redisplay to end of buffer.
(values insert-start (if one-line-p insert-mark nil) t))
;; Else punt and redisplay everything! Replace with more efficient
;; algorithm when possible.
(progn
(clear-area text)
(values 0 nil nil)))
(setf (text-extent-defined-p text) nil)
(text-refresh text refresh-start refresh-end clear-p)))))))))
(defmethod text-insert ((text edit-text) (input character))
(edit-text-insert text input))
(defmethod text-insert ((text edit-text) (input string))
(edit-text-insert text input))
(defmethod text-insert-nongraphic ((text edit-text) (char (eql #\newline)))
(edit-text-insert text char))
(defmethod text-insert-nongraphic ((text edit-text) (char (eql #\linefeed)))
(edit-text-insert text #\newline))))
(let ((prev-point (make-mark)))
(defmethod text-rubout ((text edit-text))
(with-slots (point mark gravity alignment buffer font extent-left extent-width) text
(multiple-value-bind (initial-start initial-end) (text-selection-range text)
;; Attempt to delete non-existent character?
(if
(and (not initial-start) (mark-equal point 0))
;; Yes, beep a warning.
(bell (contact-display text))
;; No, perform delete.
(while-changing-marks (text)
(move-mark prev-point point)
;; Determine initial delete range.
(setf initial-start (or initial-start (buffer-move-mark buffer point :chars -1))
initial-end (or initial-end prev-point))
;; Invoke :delete callback to determine actual delete range.
(multiple-value-bind (start end)
(apply-callback-else (text :delete text initial-start initial-end)
(values initial-start initial-end))
;; Deletion allowed?
(unless start (return-from text-rubout))
;; If delete range altered, then clear selection and do not delete it.
(unless (and (mark-equal start initial-start) (mark-equal end initial-end))
(setf (edit-text-mark text) point))
;; Clear damaged area, delete chars, then redisplay.
;;
;; Redisplay is simple and efficient for most common case ---
;; :north-west gravity, :left alignment, and delete affecting only one line.
;; Otherwise, redisplay is simple and inefficient! Replace with more
;; sophisticated algorithm when possible.
(let ((start (move-mark initial-start start))
(end (move-mark initial-end end)))
;; Clear efficiently, if possible.
(multiple-value-bind (refresh-start refresh-end clear-p)
(if
(or (and (eq gravity :north-west) (eq alignment :left))
(and (eq gravity :north-east) (eq alignment :right)))
;; Optimize this case...
(let*
((one-line-p (= (mark-line-index start) (mark-line-index end)))
(ascent (font-ascent font))
(descent (font-descent font))
(clear-start (mark-line-index start))
(line-height (+ ascent descent)))
;; Clear damaged areas. If multiple lines damaged, just clear to bottom of window.
(when (eq alignment :left)
;; This case can be optimized: clear first line only from delete point.
(text-clear-line
text
(text-base-x text clear-start (mark-index start))
(text-base-y text clear-start)))
(unless (and (eq alignment :left) one-line-p)
(when (eq alignment :left)
;; First line already cleared above.
(incf clear-start))
;; Clear one or more lines.
(clear-area
text
:x extent-left
:y (- (text-base-y text clear-start) ascent)
:width extent-width
:height (when one-line-p line-height)))
(values start (when one-line-p end) t))
;; Else punt and redisplay everything! Replace with more efficient
;; algorithm when possible.
(progn
(clear-area text)
(values 0 nil nil)))
;; Delete chars.
(buffer-delete buffer start end)
;; Redisplay buffer.
(setf (text-extent-defined-p text) nil)
(text-refresh text refresh-start refresh-end clear-p))
;; Update point and mark.
(move-mark point (move-mark mark start))))))))))